package com.blkj.iam.config;

import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.filter.SaServletFilter;
import cn.dev33.satoken.router.SaHttpMethod;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.sso.config.SaSsoServerConfig;
import cn.dev33.satoken.sso.name.ParamName;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import com.blkj.iam.common.constant.CommonConstant;
import com.blkj.iam.common.util.JwtUtil;
import com.blkj.iam.common.util.LoginIdGenerator;
import com.blkj.iam.core.security.token.JwtTokenManager;
import com.blkj.iam.system.service.UserService;
import com.dtflys.forest.Forest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * [Sa-Token 权限认证] 配置类 （解决跨域问题）
 *
 * @author click33
 */
@Slf4j
@Configuration
@RequiredArgsConstructor
public class SaTokenConfigure implements WebMvcConfigurer {

    private final UserService userService;
    private final JwtTokenManager tokenManager;
    private final ParamName paramName = new ParamName();
    private final RedisTemplate<String, Object> redisTemplate;

    /**
     * 注册 [Sa-Token 全局过滤器]
     */
    @Bean
    public SaServletFilter getSaServletFilter() {
        return new SaServletFilter()
                // 指定 [拦截路由] 与 [放行路由]
                .addInclude("/**").addExclude("/favicon.ico")

                // 认证函数: 每次请求执行
                .setAuth(this::authenticateRequest)

                // 异常处理函数：每次认证函数发生异常时执行此函数
                .setError(this::handleError)

                // 前置函数：在每次认证函数之前执行
                .setBeforeAuth(this::beforeAuth);
    }

    /**
     * 登录校验逻辑
     */
    private void authenticateRequest(Object obj) {
        String path = SaHolder.getRequest().getRequestPath();
        if (path.startsWith("/sso/")) {
            Map<String, String> paramMap = SaHolder.getRequest().getParamMap();
            log.info("----- 请求path={}  提交参数={}", path, paramMap);
            log.info("----- 请求path={}  提交token={}", path, StpUtil.getTokenValue());

            // 登录校验
//            handleDoLogin(paramMap.get("name"), paramMap.get("pwd"));
        }
    }

    /**
     * 异常处理逻辑
     */
    private SaResult handleError(Throwable e) {
        return SaResult.error(e.getMessage());
    }

    /**
     * 前置函数：在每次认证函数之前执行
     */
    private void beforeAuth(Object obj) {
        SaHolder.getResponse()
                // ---------- 设置跨域响应头 ----------
                // 允许指定域访问跨域资源
                .setHeader("Access-Control-Allow-Origin", "*")
                // 允许所有请求方式
                .setHeader("Access-Control-Allow-Methods", "*")
                // 允许的header参数
                .setHeader("Access-Control-Allow-Headers", "*")
                // 有效时间
                .setHeader("Access-Control-Max-Age", "3600");

        // 如果是预检请求，则立即返回到前端
        SaRouter.match(SaHttpMethod.OPTIONS).free(r -> log.info("--------OPTIONS预检请求，不做处理")).back();
    }

    /**
     * SSO 服务器配置
     */
    @Primary
    @Bean
    public SaSsoServerConfig ssoServerConfig() {
        SaSsoServerConfig ssoServer = new SaSsoServerConfig();

        // 配置：未登录时返回的View
        ssoServer.notLoginView = () -> {
            return new ModelAndView("sa-login.html");
        };

        // 配置：登录处理函数
        ssoServer.doLoginHandle = this::handleDoLogin;

        // 配置 Http 请求处理器 （在模式三的单点注销功能下用到，如不需要可以注释掉）
        ssoServer.sendHttp = this::sendHttpRequest;

        return ssoServer;
    }

    /**
     * 登录处理逻辑
     */
    private SaResult handleDoLogin(String name, String pwd) {
        boolean login = userService.clientLogin(name, pwd);
        if (login) {
            long loginId = LoginIdGenerator.generateFixed(name);
            StpUtil.login(loginId);

            // 生成签名
            String token = JwtUtil.sign(name, pwd);
            String loginIdAsString = StpUtil.getLoginIdAsString();
            redisTemplate.opsForValue().set(CommonConstant.CLIENT_USER_TOKEN + loginIdAsString, token);
            redisTemplate.expire(CommonConstant.CLIENT_USER_TOKEN + loginIdAsString, CommonConstant.EXPIRE_TIME_7_DAYS, TimeUnit.MILLISECONDS);

            // 打印登录信息
            String clientId = SaHolder.getRequest().getParam(paramName.client);
            String clientToken = StpUtil.getTokenValue();
            log.info("SSO登录成功, 客户端ID:{}, 登录ID:{}, 用户名:{}, 密码:{}", clientId, loginId, name, pwd);

            return SaResult.ok("登录成功！").setData(clientToken);
        } else {
            return SaResult.error("用户名或密码错误");
        }
    }

    /**
     * 发送 HTTP 请求
     */
    private String sendHttpRequest(String url) {
        try {
            log.info("------ 发起请求：{}", url);
            String resStr = Forest.get(url).executeAsString();
            log.info("------ 请求结果：{}", resStr);
            return resStr;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}